package com.mtiiot.fstcpserver.fastdfs.core;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;

public class TrackerClient
{
  protected TrackerGroup tracker_group;
  protected byte errno;

  public TrackerClient()
  {
    this.tracker_group = ClientGlobal.g_tracker_group;
  }

  public TrackerClient(TrackerGroup tracker_group)
  {
    this.tracker_group = tracker_group;
  }

  public byte getErrorCode()
  {
    return this.errno;
  }

  public TrackerServer getConnection()
    throws IOException
  {
    return this.tracker_group.getConnection();
  }

  public StorageServer getStoreStorage(TrackerServer trackerServer)
    throws IOException
  {
    String groupName = null;
    return getStoreStorage(trackerServer, groupName);
  }

  public StorageServer getStoreStorage(TrackerServer trackerServer, String groupName)
    throws IOException
  {
    boolean bNewConnection;
    if (trackerServer == null)
    {
      trackerServer = getConnection();
      if (trackerServer == null)
      {
        return null;
      }
      bNewConnection = true;
    }
    else
    {
      bNewConnection = false;
    }

    Socket trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();
    try
    {
      int out_len;
      byte cmd;
      if ((groupName == null) || (groupName.length() == 0))
      {
        cmd = 101;
        out_len = 0;
      }
      else
      {
        cmd = 104;
        out_len = 16;
      }
      byte[] header = ProtoCommon.packHeader(cmd, out_len, (byte)0);
      out.write(header);
      byte[] bs;
      if ((groupName != null) && (groupName.length() > 0))
      {
        bs = groupName.getBytes(ClientGlobal.g_charset);
        byte[] bGroupName = new byte[16];
        int group_len;
        if (bs.length <= 16)
        {
          group_len = bs.length;
        }
        else
        {
          group_len = 16;
        }
        Arrays.fill(bGroupName, (byte)0);
        System.arraycopy(bs, 0, bGroupName, 0, group_len);
        out.write(bGroupName);
      }

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(), (byte)100, 40L);

      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0)
      {
        return null;
      }

      String ip_addr = new String(pkgInfo.body, 16, 15).trim();

      int port = (int)ProtoCommon.buff2long(pkgInfo.body, 31);

      byte store_path = pkgInfo.body[39];

      return new StorageServer(ip_addr, port, store_path);
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }
    }
  }

  public StorageServer[] getStoreStorages(TrackerServer trackerServer, String groupName)
    throws IOException
  {
    boolean bNewConnection;
    if (trackerServer == null)
    {
      trackerServer = getConnection();
      if (trackerServer == null)
      {
        return null;
      }
      bNewConnection = true;
    }
    else
    {
      bNewConnection = false;
    }

    Socket trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();
    try
    {
      int out_len;
      byte cmd;
      if ((groupName == null) || (groupName.length() == 0))
      {
        cmd = 106;
        out_len = 0;
      }
      else
      {
        cmd = 107;
        out_len = 16;
      }
      byte[] header = ProtoCommon.packHeader(cmd, out_len, (byte)0);
      out.write(header);
      byte[] bs;
      if ((groupName != null) && (groupName.length() > 0))
      {
        bs = groupName.getBytes(ClientGlobal.g_charset);
        byte[] bGroupName = new byte[16];
        int group_len;
        if (bs.length <= 16)
        {
          group_len = bs.length;
        }
        else
        {
          group_len = 16;
        }
        Arrays.fill(bGroupName, (byte)0);
        System.arraycopy(bs, 0, bGroupName, 0, group_len);
        out.write(bGroupName);
      }

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(), (byte)100, -1L);

      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0)
      {
        return null;
      }

      if (pkgInfo.body.length < 40)
      {
        this.errno = 22;
        return null;
      }

      int ipPortLen = pkgInfo.body.length - 17;
      int recordLength = 23;

      if (ipPortLen % 23 != 0)
      {
        this.errno = 22;
        return null;
      }

      int serverCount = ipPortLen / 23;
      if (serverCount > 16)
      {
        this.errno = 28;
        return null;
      }

      StorageServer[] results = new StorageServer[serverCount];
      byte store_path = pkgInfo.body[(pkgInfo.body.length - 1)];
      int offset = 16;

      for (int i = 0; i < serverCount; i++)
      {
        String ip_addr = new String(pkgInfo.body, offset, 15).trim();
        offset += 15;

        int port = (int)ProtoCommon.buff2long(pkgInfo.body, offset);
        offset += 8;

        results[i] = new StorageServer(ip_addr, port, store_path);
      }

      return results;
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }
    }
  }

  public StorageServer getFetchStorage(TrackerServer trackerServer, String groupName, String filename)
    throws IOException
  {
    ServerInfo[] servers = getStorages(trackerServer, (byte)102, groupName, filename);

    if (servers == null)
    {
      return null;
    }

    return new StorageServer(servers[0].getIpAddr(), servers[0].getPort(), 0);
  }

  public StorageServer getUpdateStorage(TrackerServer trackerServer, String groupName, String filename)
    throws IOException
  {
    ServerInfo[] servers = getStorages(trackerServer, (byte)103, groupName, filename);

    if (servers == null)
    {
      return null;
    }

    return new StorageServer(servers[0].getIpAddr(), servers[0].getPort(), 0);
  }

  public ServerInfo[] getFetchStorages(TrackerServer trackerServer, String groupName, String filename)
    throws IOException
  {
    return getStorages(trackerServer, (byte)105, groupName, filename);
  }

  protected ServerInfo[] getStorages(TrackerServer trackerServer, byte cmd, String groupName, String filename)
    throws IOException
  {
    boolean bNewConnection;
    if (trackerServer == null)
    {
      trackerServer = getConnection();
      if (trackerServer == null)
      {
        return null;
      }
      bNewConnection = true;
    }
    else
    {
      bNewConnection = false;
    }
    Socket trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();
    try
    {
      byte[] bs = groupName.getBytes(ClientGlobal.g_charset);
      byte[] bGroupName = new byte[16];
      byte[] bFileName = filename.getBytes(ClientGlobal.g_charset);
      int len;
      if (bs.length <= 16)
      {
        len = bs.length;
      }
      else
      {
        len = 16;
      }
      Arrays.fill(bGroupName, (byte)0);
      System.arraycopy(bs, 0, bGroupName, 0, len);

      byte[] header = ProtoCommon.packHeader(cmd, 16 + bFileName.length, (byte)0);
      byte[] wholePkg = new byte[header.length + bGroupName.length + bFileName.length];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      System.arraycopy(bGroupName, 0, wholePkg, header.length, bGroupName.length);
      System.arraycopy(bFileName, 0, wholePkg, header.length + bGroupName.length, bFileName.length);
      out.write(wholePkg);

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(), (byte)100, -1L);

      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0)
      {
        return null;
      }

      if (pkgInfo.body.length < 39)
      {
        throw new IOException("Invalid body length: " + pkgInfo.body.length);
      }

      if ((pkgInfo.body.length - 39) % 15 != 0)
      {
        throw new IOException("Invalid body length: " + pkgInfo.body.length);
      }

      int server_count = 1 + (pkgInfo.body.length - 39) / 15;

      String ip_addr = new String(pkgInfo.body, 16, 15).trim();
      int offset = 31;

      int port = (int)ProtoCommon.buff2long(pkgInfo.body, offset);
      offset += 8;

      ServerInfo[] servers = new ServerInfo[server_count];
      servers[0] = new ServerInfo(ip_addr, port);
      for (int i = 1; i < server_count; i++)
      {
        servers[i] = new ServerInfo(new String(pkgInfo.body, offset, 15).trim(), port);
        offset += 15;
      }

      return servers;
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }
    }
  }

  public StorageServer getFetchStorage1(TrackerServer trackerServer, String file_id)
    throws IOException
  {
    String[] parts = new String[2];
    this.errno = StorageClient1.split_file_id(file_id, parts);
    if (this.errno != 0)
    {
      return null;
    }

    return getFetchStorage(trackerServer, parts[0], parts[1]);
  }

  public ServerInfo[] getFetchStorages1(TrackerServer trackerServer, String file_id)
    throws IOException
  {
    String[] parts = new String[2];
    this.errno = StorageClient1.split_file_id(file_id, parts);
    if (this.errno != 0)
    {
      return null;
    }

    return getFetchStorages(trackerServer, parts[0], parts[1]);
  }

  public StructGroupStat[] listGroups(TrackerServer trackerServer)
    throws IOException
  {
    boolean bNewConnection;
    if (trackerServer == null)
    {
      trackerServer = getConnection();
      if (trackerServer == null)
      {
        return null;
      }
      bNewConnection = true;
    }
    else
    {
      bNewConnection = false;
    }

    Socket trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();
    try
    {
      byte[] header = ProtoCommon.packHeader((byte)91, 0L, (byte)0);
      out.write(header);

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(), (byte)100, -1L);

      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0)
      {
        return null;
      }

      ProtoStructDecoder decoder = new ProtoStructDecoder();
      return (StructGroupStat[])decoder.decode(pkgInfo.body, StructGroupStat.class, StructGroupStat.getFieldsTotalSize());
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }

      throw ex;
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
      this.errno = 22;
      return null;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }
    }
  }

  public StructStorageStat[] listStorages(TrackerServer trackerServer, String groupName)
    throws IOException
  {
    String storageIpAddr = null;
    return listStorages(trackerServer, groupName, storageIpAddr);
  }

  public StructStorageStat[] listStorages(TrackerServer trackerServer, String groupName, String storageIpAddr)
    throws IOException
  {
    boolean bNewConnection;
    if (trackerServer == null)
    {
      trackerServer = getConnection();
      if (trackerServer == null)
      {
        return null;
      }
      bNewConnection = true;
    }
    else
    {
      bNewConnection = false;
    }
    Socket trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();
    try
    {
      byte[] bs = groupName.getBytes(ClientGlobal.g_charset);
      byte[] bGroupName = new byte[16];
      int len;
      if (bs.length <= 16)
      {
        len = bs.length;
      }
      else
      {
        len = 16;
      }
      Arrays.fill(bGroupName, (byte)0);
      System.arraycopy(bs, 0, bGroupName, 0, len);
      int ipAddrLen;
      byte[] bIpAddr;
      if ((storageIpAddr != null) && (storageIpAddr.length() > 0))
      {
        bIpAddr = storageIpAddr.getBytes(ClientGlobal.g_charset);
        if (bIpAddr.length < 16)
        {
          ipAddrLen = bIpAddr.length;
        }
        else
        {
          ipAddrLen = 15;
        }
      }
      else
      {
        bIpAddr = null;
        ipAddrLen = 0;
      }

      byte[] header = ProtoCommon.packHeader((byte)92, 16 + ipAddrLen, (byte)0);
      byte[] wholePkg = new byte[header.length + bGroupName.length + ipAddrLen];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      System.arraycopy(bGroupName, 0, wholePkg, header.length, bGroupName.length);
      if (ipAddrLen > 0)
      {
        System.arraycopy(bIpAddr, 0, wholePkg, header.length + bGroupName.length, ipAddrLen);
      }
      out.write(wholePkg);

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(), (byte)100, -1L);

      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0)
      {
        return null;
      }

      ProtoStructDecoder decoder = new ProtoStructDecoder();
      return (StructStorageStat[])decoder.decode(pkgInfo.body, StructStorageStat.class, StructStorageStat.getFieldsTotalSize());
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }

      throw ex;
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
      this.errno = 22;
      return null;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }
    }
  }

  private boolean deleteStorage(TrackerServer trackerServer, String groupName, String storageIpAddr)
    throws IOException
  {
    Socket trackerSocket = trackerServer.getSocket();
    OutputStream out = trackerSocket.getOutputStream();

    byte[] bs = groupName.getBytes(ClientGlobal.g_charset);
    byte[] bGroupName = new byte[16];
    int len;
    if (bs.length <= 16)
    {
      len = bs.length;
    }
    else
    {
      len = 16;
    }
    Arrays.fill(bGroupName, (byte)0);
    System.arraycopy(bs, 0, bGroupName, 0, len);

    byte[] bIpAddr = storageIpAddr.getBytes(ClientGlobal.g_charset);
    int ipAddrLen;
    if (bIpAddr.length < 16)
    {
      ipAddrLen = bIpAddr.length;
    }
    else
    {
      ipAddrLen = 15;
    }

    byte[] header = ProtoCommon.packHeader((byte)93, 16 + ipAddrLen, (byte)0);
    byte[] wholePkg = new byte[header.length + bGroupName.length + ipAddrLen];
    System.arraycopy(header, 0, wholePkg, 0, header.length);
    System.arraycopy(bGroupName, 0, wholePkg, header.length, bGroupName.length);
    System.arraycopy(bIpAddr, 0, wholePkg, header.length + bGroupName.length, ipAddrLen);
    out.write(wholePkg);

    ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(trackerSocket.getInputStream(), (byte)100, 0L);

    this.errno = pkgInfo.errno;
    return pkgInfo.errno == 0;
  }

  public boolean deleteStorage(String groupName, String storageIpAddr)
    throws IOException
  {
    return deleteStorage(ClientGlobal.g_tracker_group, groupName, storageIpAddr);
  }

  public boolean deleteStorage(TrackerGroup trackerGroup, String groupName, String storageIpAddr)
    throws IOException
  {
    int notFoundCount = 0;
    TrackerServer trackerServer;
    for (int serverIndex = 0; serverIndex < trackerGroup.tracker_servers.length; serverIndex++)
    {
      try
      {
        trackerServer = trackerGroup.getConnection(serverIndex);
      }
      catch (IOException ex)
      {
        ex.printStackTrace(System.err);
        this.errno = 61;
        return false;
      }

      try
      {
        StructStorageStat[] storageStats = listStorages(trackerServer, groupName, storageIpAddr);
        boolean bool;
        if (storageStats == null)
        {
          if (this.errno == 2)
          {
            notFoundCount++;
          }
          else
          {
            return false;
          }
        }
        else if (storageStats.length == 0)
        {
          notFoundCount++;
        }
        else if ((storageStats[0].getStatus() == 6) || (storageStats[0].getStatus() == 7))
        {
          this.errno = 16;
          return false;
        }
      }
      finally
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }
    }

    if (notFoundCount == trackerGroup.tracker_servers.length)
    {
      this.errno = 2;
      return false;
    }

    notFoundCount = 0;
    for (int serverIndex = 0; serverIndex < trackerGroup.tracker_servers.length; serverIndex++)
    {
      try
      {
        trackerServer = trackerGroup.getConnection(serverIndex);
      }
      catch (IOException ex)
      {
        System.err.println("connect to server " + trackerGroup.tracker_servers[serverIndex].getAddress().getHostAddress() + ":" + trackerGroup.tracker_servers[serverIndex].getPort() + " fail");
        ex.printStackTrace(System.err);
        this.errno = 61;
        return false;
      }

      try
      {
        if (!deleteStorage(trackerServer, groupName, storageIpAddr))
        {
          if (this.errno != 0)
          {
            if (this.errno == 2)
            {
              notFoundCount++;
            }
            else if (this.errno != 114)
            {
            	 return this.errno == 0;
            }
          }
        }
      }
      finally
      {
        try
        {
          trackerServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
      }
    }

    if (notFoundCount == trackerGroup.tracker_servers.length)
    {
      this.errno = 2;
      return false;
    }

    if (this.errno == 2)
    {
      this.errno = 0;
    }

    return this.errno == 0;
  }
}